package tool

import java.io.File
import java.net.URLEncoder
import java.nio.file.Files
import java.util.concurrent.ForkJoinPool
import dao.{ConfigDao, ModeDao, SampleDao}
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import play.api.libs.Files.TemporaryFile
import play.api.mvc.{MultipartFormData, Request, RequestHeader}
import tool.Pojo.{BasicData, BatchMissionData, CommandData, IndexData, MissionData, MyBatchDataDir, MyDao, MyData, MyDataDir, StrSite}
import utils.Utils

import scala.collection.JavaConverters._
import implicits.Implicits._
import play.api.libs.json.Json
import shared.Pojo.{SeqData, SnpReadsData, StrReadsData}

import scala.collection.parallel.ForkJoinTaskSupport
import scala.collection.parallel.CollectionConverters._
import scala.xml.{Node, Utility, XML}
import scala.xml
import scala.xml.Elem._
import shared.VarTool._

import scala.collection.parallel.ParSeq
import models.Tables._
import shapeless.ops.record.ToMap
import shapeless.{HList, LabelledGeneric}

import scala.concurrent.ExecutionContext.Implicits.global

/**
 * Created by Administrator on 2019/12/5
 */
object Tool extends ProjectTool with ProjectFileTool {

  val splitFileSuffix = ".fq.gz"

  val dbName = "forensic_database"
  val windowsPath = s"C:\\${dbName}"
  val playPath = new File("../").getAbsolutePath
  val linuxPath = playPath + s"/${dbName}"
  val isWindows = {
    if (new File(windowsPath).exists()) true else false
  }
  val path = {
    if (new File(windowsPath).exists()) windowsPath else linuxPath
  }
  val windowsTestDir = new File("C:\\temp")
  val linuxTestDir = new File(playPath, "workspace")
  val testDir = if (windowsTestDir.exists()) windowsTestDir else linuxTestDir
  val exampleDir = new File(path, "example")
  val userDir = new File(path, "user")
  val binDir = new File(path, "bin")
  val dataDir = new File(path, "data")
  val fastxBinDir = new File(binDir, "fastx_toolkit/bin")

  val jarPath = {
    val inPath = "C:\\workspaceForIDEA\\forensic_fast_pg\\server\\jars"
    val linuxInPath = linuxPath + "/jars"
    if (new File(inPath).exists()) inPath else linuxInPath
  }

  val yarnJarDir = new File(jarPath, "yarn")
  val setAJarDir = new File(jarPath, "setA")
  val setBJarDir = new File(jarPath, "setB")

  val pyPath = {
    val inPath = "C:\\workspaceForIDEA\\forensic_fast\\server\\pyScripts"
    val linuxInPath = linuxPath + "/pyScripts"
    if (new File(inPath).exists()) inPath else linuxInPath
  }

  def getInfoByFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val columnNames = lines.head.split("\t").drop(1)
    val array = lines.drop(1).map { line =>
      val columns = line.split("\t")
      val map = Map("geneId" -> columns(0))
      val otherMap = columnNames.zip(columns.drop(1)).map { case (columnName, data) =>
        (columnName -> data)
      }.toMap
      map ++ otherMap
    }
    (columnNames, array)
  }

  def generateMissionName = {
    (new DateTime).toString("yyyyMMddHHmmss")
  }

  def createTempDirectory(prefix: String)(implicit modeDao: ModeDao) = {
    if (isTestMode) Tool.testDir else Files.createTempDirectory(prefix).toFile
  }

  def isTestMode(implicit modeDao: ModeDao) = {
    val mode = Utils.execFuture(modeDao.select)
    if (mode.test == "t") true else false
  }

  def getUserIdDir(userId: Int) = {
    new File(Tool.userDir, userId.toString)
  }

  def deleteDirectory(direcotry: File)(implicit modeDao: ModeDao) = {
    if (!isTestMode) Utils.deleteDirectory(direcotry)
  }

  def getDataDir(dataDir: File)(implicit request: Request[MultipartFormData[TemporaryFile]]) = {
    val fqFile = Tool.fileMoveDir("fqFile", dataDir)
    MyDataDir(dataDir, fqFile)
  }

  def getBatchDataDir(dataDir: File, data: BatchMissionData)(implicit request: Request[MultipartFormData[TemporaryFile]]) = {
    val gzFile = new File(data.serverGzFile)
    val indexFile = Tool.fileMoveDir("indexFile", dataDir)
    MyBatchDataDir(dataDir, gzFile, indexFile)
  }

  def getLogFile(dir: File) = {
    val file = new File(dir, "log.txt")
    "Run successfully!".toFile(file)
    file
  }

  def getInputXmlFile(dir: File) = {
    new File(dir, "input.xml")
  }

  def getInputTxtFile(dir: File) = {
    new File(dir, "input.txt")
  }

  def getGzFile(dir: File) = {
    new File(dir, "data.gz")
  }

  def getIndexFile(dir: File) = {
    new File(dir, "index.txt")
  }

  def getSplitWorkspaceDir(dir: File) = {
    new File(dir, "split")
  }

  def getFyWorkspaceDir(dir: File) = {
    new File(dir, "fy")
  }

  def getSplitResultDir(dir: File) = {
    new File(dir, "out")
  }

  def getOutputXmlFile(dir: File) = {
    new File(dir, "output.xml")
  }

  def getUserId(implicit request: RequestHeader) = {
    request.session.get("id").get.toInt
  }

  def getUserIdDir(implicit request: RequestHeader) = {
    val userId = getUserId
    new File(Tool.userDir, userId.toString)
  }

  def fy(tmpDir: File) = {
    val inputFile = Tool.getInputXmlFile(tmpDir)
    val inputFaFile = Tool.getInputTxtFile(tmpDir)
    val outFile = Tool.getOutputXmlFile(tmpDir)
    println(Tool.jarPath)
    val command =
      s"""
         |${Tool.fastxBinDir.unixPath}/fastx_quality_stats -i ${inputFaFile.unixPath} -o stat.txt -Q 33
         |java -jar  ${new File(Tool.jarPath, "fy-1.0-SNAPSHOT.jar").unixPath} fastqToXml -i ${inputFile.getName} -o ${outFile.unixPath}
           """.stripMargin
    CommandData(tmpDir, List(command))
  }

  def split(workspaceDir: File) = {
    val gzFile = Tool.getGzFile(workspaceDir)
    val indexFile = Tool.getIndexFile(workspaceDir)
    val splitWorkspaceDir = Tool.getSplitWorkspaceDir(workspaceDir)
    List(indexFile).foreach(_.fileCopyToDir(splitWorkspaceDir))
    val indexLines = indexFile.txtLines
    val outDir = new File(splitWorkspaceDir, "out").createDirectoryWhenNoExist
    val commands = indexLines.grouped(1000).toList.zipWithIndex.map { case (inLines, i) =>
      val inIndexFile = new File(splitWorkspaceDir, s"index_${i}.txt")
      inLines.toTxtFile(inIndexFile)
      s"""
         |python2 ${Tool.pyPath.unixPath}/gSplitFastQIndex.py -o ${outDir.unixPath} -i ${inIndexFile.unixPath} -l 10 --bases_after_index AACTCCTTGGCTCACA -v --summary ${gzFile.unixPath}
           """.stripMargin
    }
    CommandData(splitWorkspaceDir, commands)
  }

  def getMissionDefaultThreadNum(implicit dao: MyDao) = {
    Utils.execFuture(dao.configDao.selectThreadNum).value.toInt
  }

  def getGzDir(implicit dao: MyDao) = {
    val gzPath = Utils.execFuture(dao.configDao.selectGzPath)
    new File(gzPath)
  }

  def noSplitBatchFys(workspaceDir: File)(implicit dao: MyDao) = {
    val splitWorkspaceDir = Tool.getSplitWorkspaceDir(workspaceDir)
    val splitResultDir = Tool.getSplitResultDir(splitWorkspaceDir)
    val fyWorkspaceDir = Tool.getFyWorkspaceDir(workspaceDir)
    val threadNum = Tool.getMissionDefaultThreadNum
    splitResultDir.listFiles().toList.filter(file => file.getName != "data_unknown.txt").map { file =>
      val id = file.getName.replaceAll("^data_", "").replaceAll(".txt$", "")
      val fyInDir = new File(fyWorkspaceDir, id)
      val inputFaFile = Tool.getInputTxtFile(fyInDir)
      val inputFile = Tool.productInputXmlFile(fyInDir)
      val outFile = Tool.getOutputXmlFile(fyInDir)
      file.copyTo(inputFaFile)

      //      val jarFile = new File(Tool.jarPath, "fy-1.0-SNAPSHOT.jar")
      //      jarFile.fileCopyToDir(fyInDir)
      //      val resourceDir = new File(Tool.jarPath, "resource")
      //      resourceDir.dirCopyToDir(fyInDir)
      //
      //      val command =
      //        s"""
      //           |${Tool.fastxBinDir.unixPath}/fastx_quality_stats -i ${inputFaFile.unixPath} -o stat.txt -Q 33
      //           |java -jar fy-1.0-SNAPSHOT.jar fastqToXml -i ${inputFile.getName} -o ${outFile.unixPath}
      //                 """.stripMargin

      val command =
        s"""
           |${Tool.fastxBinDir.unixPath}/fastx_quality_stats -i ${inputFaFile.unixPath} -o stat.txt -Q 33
           |java -jar ${Tool.jarPath.unixPath}/setA-0.9.2.jar fastqToXml -i ${inputFile.getName} -o ${outFile.unixPath}
                       """.stripMargin

      CommandData(fyInDir, List(command))
    }.par.threadNum(threadNum)
  }

  def getConfigFile(workspaceDir: File) = {
    new File(workspaceDir, "config.json")
  }

  def produceConfigFile[T, R <: HList](configData: T, workspaceDir: File)(implicit gen: LabelledGeneric.Aux[T, R],
                                                                          toMap: ToMap.Aux[R, Symbol, Any]) = {
    val configFile = getConfigFile(workspaceDir)
    val json = Utils.getJsonByT(configData)
    Json.stringify(json).toFile(configFile)
    configFile
  }

  def productInputXmlFile(workspaceDir: File, data: MissionData) = {
    val fqFile = Tool.getInputTxtFile(workspaceDir)
    val xml =
      s"""
         | <data>
         |        <sample>
         |          <basicInfo>
         |            <Lane>${data.lane}</Lane>
         |            <id>${data.id}</id>
         |            <gender>${data.gender}</gender>
         |            <type>${data.kind}</type>
         |            <fq>${fqFile.unixPath}</fq>
         |          </basicInfo>
         |        </sample>
         |      </data>
         |""".stripMargin

    val inputXmlFile = Tool.getInputXmlFile(workspaceDir)
    xml.toFile(inputXmlFile)
  }

  def productInputXmlFile(workspaceDir: File) = {
    val fqFile = Tool.getInputTxtFile(workspaceDir)
    val xml =
      s"""
         | <data>
         |        <sample>
         |          <basicInfo>
         |            <Lane></Lane>
         |            <id></id>
         |            <gender></gender>
         |            <type></type>
         |            <fq>${fqFile.unixPath}</fq>
         |          </basicInfo>
         |        </sample>
         |      </data>
         |""".stripMargin

    val inputXmlFile = Tool.getInputXmlFile(workspaceDir)
    xml.toFile(inputXmlFile)
  }

  def getSnpReadsFile(dir: File) = {
    new File(dir, "snp_reads.txt")
  }

  def getStrGenotypeFile(dir: File) = {
    new File(dir, "str_genotype.txt")
  }

  def getSnpGenotypeFile(dir: File) = {
    new File(dir, "snp_genotype.txt")
  }

  def getBasicFile(dir: File) = {
    new File(dir, "basic.txt")
  }

  def getStatFile(dir: File) = {
    new File(dir, "stat.txt")
  }

  def getRootDir(implicit configDao: ConfigDao) = {
    val f = configDao.selectRootPath
    val rootPath = Utils.execFuture(f).value
    new File(rootPath)
  }

  def getUserSampleDir(userId: Int) = {
    val userIdDir = Tool.getUserIdDir(userId)
    new File(userIdDir, "sample")
  }

  def getSampleIdDir(row: SampleRow) = {
    val userMissionFile = getUserSampleDir(row.userId)
    new File(userMissionFile, row.id)
  }

  def getSampleIdDir(userId: Int, sampleId: String) = {
    val userMissionFile = getUserSampleDir(userId)
    new File(userMissionFile, sampleId)
  }

  def toBasicFile(datas: List[MyData], dir: File) = {
    val newLines = List("Total Depth", "Available Depth", "Interlocus Balance", "Single Source", "iSNP Loci Typed",
      "Autosomal Loci Typed", "X Loci Typed", "Y Loci Typed",
      "STR Average Depth", "STR Depth STD", "STR Num. of Depth < 30", "STR Num. of Depth < 100",
      "iSNP Average Depth", "iSNP Depth STD") :: datas.map { row =>
      List(row.totalDepth, row.availableDepth, row.interlocusBalance, row.singleSource,
        row.iSNPLociTyped, row.autosomalLociTyped, row.xLociTyped, row.yLociTyped,
        row.strAvg, row.strSTD, row.strDepthBelow30, row.strDepthBelow100,
        row.snpAvg, row.snpSTD)
    }
    newLines.toTxtFile(Tool.getBasicFile(dir))
  }

  def produceReadsFile(dir: File) = {
    val xmlFile = new File(dir, "output.xml")
    val xml = XML.loadFile(xmlFile)
    val strReadsDatas = Tool.produceReadsDatas(xml)
    val newLines = Utils.getLinesByTs(strReadsDatas)
    newLines.toTxtFile(Tool.getReadsFile(dir))
  }

  def produceReadsDataMap(file: File) = {
    val xml = XML.loadFile(file)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      println(id)
      val strReadsDatas = Tool.produceReadsDatas(sampleNode)
      (id -> strReadsDatas)
    }.toMap
  }

  def fileMoveDir(fieldName: String, dir: File)(implicit request: Request[MultipartFormData[TemporaryFile]]) = {
    fileMoveDirOp(fieldName, dir).get
  }

  def fileMoveDirOp(fieldName: String, dir: File)(implicit request: Request[MultipartFormData[TemporaryFile]]) = {
    val tempFile = request.body.file(fieldName).get
    if (tempFile.ref.path.toFile.length() > 0) {
      val destFile = new File(dir, tempFile.filename)
      tempFile.ref.moveTo(destFile, replace = true)
      Some(destFile)
    } else None
  }

  def produceSeqDataMap(file: File) = {
    val xml = XML.loadFile(file)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val seqDatas = Tool.produceSeqDatas(sampleNode)
      (id, seqDatas)
    }.toMap
  }

  def produceSeqDatas(sampleNode: Node) = {
    val autoStrs = (sampleNode \\ "Autosomal_STR" \\ "autoStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      (autoNode \\ "genotype").map { node =>
        (locus, node.text)
      }
    }
    val yStrs = (sampleNode \\ "Y_STR" \\ "yStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      (autoNode \\ "genotype").map { node =>
        (locus, node.text)
      }
    }
    val xStrs = (sampleNode \\ "X_STR" \\ "xStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      (autoNode \\ "genotype").map { node =>
        (locus, node.text)
      }
    }

    val strSiteMap = (sampleNode \\ "strSites" \\ "site").toList.map { node =>
      val locus = (node \ "@locus").text
      val genoType = (node \ "Genotype").text
      val reads = (node \ "Reads").text.toDouble.toInt
      val typed = (node \ "Typed").text
      val repeatSeq = (node \ "RepeatSequence").text
      val snp = (node \ "snp").text
      StrSite(locus, genoType, typed, reads.toString, repeatSeq,snp=snp)
    }.map { x =>
      val t = (x.locus, x.genoType)
      (t, x)
    }.toMap
    val strs = autoStrs ::: yStrs ::: xStrs

    strs.map { t =>
      val row = strSiteMap(t)
      SeqData(row.locus, row.genoType, row.typed, row.reads, row.repeatSeq,snp=row.snp)
    }
  }

  def produceSnpSeqDataMap(xmlFile: File) = {
    val xml = XML.loadFile(xmlFile)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val seqDatas = Tool.produceSnpSeqDatas(sampleNode)
      (id, seqDatas)
    }.toMap
  }

  def produceSnpSeqDatas(sampleNode: Node) = {
    case class StrSite(locus: String, genoType: String, typed: String, reads: String, repeatSeq: String)
    val id = Tool.parseGetId(sampleNode)
    val snps = (sampleNode \\ "iSNP" \\ "isnp").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      (autoNode \\ "genotype").map { node =>
        (locus, node.text)
      }
    }

    val strSiteMap = (sampleNode \\ "snpSites" \\ "site").toList.map { node =>
      val locus = (node \ "@locus").text
      val genoType = (node \ "Genotype").text
      val reads = (node \ "Reads").text.toDouble.toInt
      val typed = (node \ "Typed").text
      val repeatSeq = (node \ "RepeatSequence").text
      StrSite(locus, genoType, typed, reads.toString, repeatSeq)
    }.map { x =>
      val t = (x.locus, x.genoType)
      (t, x)
    }.toMap
    snps.map { t =>
      val row = strSiteMap(t)
      SeqData(row.locus, row.genoType, row.typed, row.reads, row.repeatSeq)
    }
  }

  def produceBasicFile(dir: File) = {
    val xmlFile = new File(dir, "output.xml")
    val xml = XML.loadFile(xmlFile)
    val datas = Tool.produceBasicDatas(xml)
    Tool.toBasicFile(datas, dir)
  }

  def produceBasicDataMap(xmlFile: File) = {
    val xml = XML.loadFile(xmlFile)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val datas = Tool.produceBasicDatas(sampleNode)
      (id, datas)
    }.toMap
  }

  def produceBasicDatas(sampleNode: Node) = {
    val id = Tool.parseGetId(sampleNode)
    (sampleNode \\ "sample" \\ "calResult").toList.map { node =>
      val availableDepth = (node \ "availableDepth").text.toDouble.toInt.toString
      val totalDepth = (node \ "totalDepth").text.toDouble.toInt.toString
      val interlocusBalance = (node \ "interlocusBalance").text
      val singleSource = (node \ "singleSource").text
      val iSNP_Loci_Typed = (node \ "iSNP_Loci_Typed").text
      val auto_Loci_Typed = (node \ "auto_Loci_Typed").text
      val x_Loci_Typed = (node \ "x_Loci_Typed").text
      val y_Loci_Typed = (node \ "y_Loci_Typed").text
      val strAvg = (node \ "strAvg").text.toDouble.toFixed(2)
      val snpAvg = (node \ "snpAvg").text.toDouble.toFixed(2)
      val strSTD = (node \ "strSTD").text.toDouble.toFixed(2)
      val snpSTD = (node \ "snpSTD").text.toDouble.toFixed(2)
      val strDepthBelow30 = (node \ "strDepthBelow30").text.toDouble.toInt.toString
      val strDepthBelow100 = (node \ "strDepthBelow100").text.toDouble.toInt.toString
      MyData(availableDepth, totalDepth, interlocusBalance, singleSource,
        iSNP_Loci_Typed, auto_Loci_Typed, x_Loci_Typed, y_Loci_Typed, strAvg, snpAvg,
        strSTD, snpSTD, strDepthBelow30, strDepthBelow100)
    }
  }


  def produceSnpReadsFile(dir: File) = {
    val xmlFile = new File(dir, "output.xml")
    val xml = XML.loadFile(xmlFile)
    val snpReadsDatas = Tool.produceSnpReadsDatas(xml)
    val newLines = Utils.getLinesByTs(snpReadsDatas)
    newLines.toTxtFile(Tool.getSnpReadsFile(dir))
  }

  def produceSnpReadsDataMap(xmlFile: File) = {
    val xml = XML.loadFile(xmlFile)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val snpReadsDatas = Tool.produceSnpReadsDatas(sampleNode)
      (id, snpReadsDatas)
    }.toMap
  }

  def produceSnpReadsDatas(sampleNode: Node) = {
    case class SiteData(locus: String, productSize: String, genoType: String, reads: String)
    val snpSites = (sampleNode \\ "snpSites" \\ "site").toList.map { node =>
      val locus = (node \ "@locus").text
      val productSize = (node \ "@product_size").text
      val genoType = (node \ "Genotype").text
      val reads = (node \ "Reads").text.toDouble.toInt
      SiteData(locus, productSize, genoType, reads.toString)
    }
    val snpMap = snpSites.map { site =>
      val t = (site.locus, site.genoType)
      (t, site.reads)
    }.toMap
    val snps = (sampleNode \\ "iSNP" \\ "isnp").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      (autoNode \\ "genotype").map { node =>
        (locus, node.text)
      }
    }

    val siteMap = snpSites.map { x =>
      val t = (x.locus, x.genoType)
      (t, x)
    }.toMap

    val locusMap = (sampleNode \\ "iSNP" \\ "isnp").toList.map { autoNode =>
      val locus = (autoNode \ "@Locus").text
      val (qc, level) = getQcLevel(autoNode)
      (locus, (qc, level))
    }.toMap

    snps.map { t =>
      val autoStr = siteMap(t)
      val qcLevelT = locusMap.getOrElse(t._1, ("", ""))
      SnpReadsData(locus = t._1, genotype = t._2, reads = snpMap(t), qc = qcLevelT._1,
        productSize = autoStr.productSize, level = qcLevelT._2)
    }
  }

  def getContentDisposition(url: String) = {
    val encodeUrl = urlEncode(url)
    s"attachment; filename*=utf-8''${encodeUrl}"
  }

  def urlEncode(url: String) = {
    URLEncoder.encode(url, "UTF-8")
  }

}
